package dts_tcp

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
)

type Fn[T any] func(T)

type Call[T any] struct {
	Name string
	Code Code
	Fn   Fn[T]
}

func (c Call[T]) Key() string {
	return setCallKey(c.Name, c.Code)
}

const (
	callSeparator = "⚠"
)

var (
	callback   sync.Map
	setCallKey = func(name string, code Code) string {
		return fmt.Sprintf("%s%s%d", name, callSeparator, code)
	}
	getCallKey = func(key string) (name string, code Code) {
		keys := strings.Split(key, callSeparator)
		if len(keys) == 2 {
			code, err := strconv.Atoi(keys[1])
			if err != nil {
				return "", 0
			}
			return keys[0], Code(code)
		}
		return "", 0
	}
)

func NewCall[T any](name string, code Code, fn Fn[T]) Call[T] {
	return Call[T]{Name: name, Code: code, Fn: fn}
}

func LoadCallsFilterCodeRun[T any](code Code, data T) {
	callback.Range(func(k, v any) bool {
		_, c := getCallKey(k.(string))
		if c == code {
			go v.(Call[T]).Fn(data)
		}
		return true
	})
}

func LoadCallsFilterCode[T any](code Code) []Call[T] {
	var res []Call[T]
	callback.Range(func(k, v any) bool {
		_, c := getCallKey(k.(string))
		if c == code {
			res = append(res, v.(Call[T]))
		}
		return true
	})
	return res
}

func LoadCalls[T any]() []Call[T] {
	var res []Call[T]
	callback.Range(func(k, v any) bool {
		res = append(res, v.(Call[T]))
		return true
	})
	return res
}

func StoreRequest[V any](name string, code Code, fn Fn[V]) {
	call := NewCall(name, code, fn)
	callback.Store(call.Key(), call)
}

func Notify[V any](code Code, fn Fn[V]) {
	call := NewCall("nil", code, fn)
	callback.Store(call.Key(), call)
}

func StoreNewCall[V any](name string, code Code, fn Fn[V]) {
	call := NewCall(name, code, fn)
	callback.Store(call.Key(), call)
}

func StoreCall[V any](v Call[V]) {
	callback.Store(v.Key(), v)
}

func RemoveCall[V any](v Call[V]) {
	callback.Delete(v.Key())
}

func RemoveNewCall(name string, code Code) {
	callback.Delete(setCallKey(name, code))
}

func RemoveAllCalls(name string) {
	callback.Range(func(key, value any) bool {
		k, _ := getCallKey(key.(string))
		if k == name {
			callback.Delete(k)
		}
		return true
	})
}

func Range(f func(name string, code Code)) {
	callback.Range(func(key, value any) bool {
		name, code := getCallKey(key.(string))
		f(name, code)
		return true
	})
}
